package de.richsource.gwtplug.client;

/* 
 * Copyright 2011 Steffen Schäfer
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import java.util.ArrayList;

import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JavaScriptObject;

/**
 * Endpoint of the pluginsystem representing one plugin. Plugins can use this
 * class to export and import components shared between plugins.
 * <br>
 * <h1>Usage:</h1>
 * To export a component to the plugin system, you need to initialize the plugin system and then call export for your instance to export.
 * <code>
 PluginComponentManager.initialize();
 PluginComponentManager.export(new PluginServiceImpl());
 * </code>
 * To import a component, you need to register a listener at the {@link PluginComponentManager} and initialize the plugn system afterwards.
 * <code>
   PluginComponentManager.addListener(new PluginComponentRegisteredListener() {
		@Override
		public void pluginComponentRegistered(Object importedComponent) {
			if(importedComponent instanceof ExampleService) {
				// do some cool stuff ...
			}
		}
	});
	PluginComponentManager.initialize();
 * </code>
 * 
 * @author Steffen Schäfer
 * 
 */
public class PluginComponentManager extends JavaScriptObject {

	public interface PluginComponentRegisteredListener {
		void pluginComponentRegistered(Object importedComponent);
	}

	private static ArrayList<PluginComponentRegisteredListener> listeners = new ArrayList<PluginComponentManager.PluginComponentRegisteredListener>();

	private static PluginComponentManager instance;

	protected PluginComponentManager() {

	}

	protected static PluginComponentManager getInstance() {
		return instance;
	}

	/**
	 * Initializes the plugin system for the current plugin and registers it for
	 * new Components.
	 */
	public static void initialize() {
		if (instance == null) {
			instance = create();
			instance.initMethods();
			PluginRegistry.get().add(instance);
		}
	}

	private static native PluginComponentManager create() /*-{
		return new Object();
	}-*/;

	/**
	 * Adds necessary native methods as callback for other plugins as
	 * communication channel for component registration.
	 */
	private final native void initMethods() /*-{
		this.handleNewComponent = function(instance) {
			@de.richsource.gwtplug.client.PluginComponentManager::handleNewComponent(Lcom/google/gwt/core/client/JavaScriptObject;)(instance);
		}
	}-*/;

	/**
	 * Factory instance to generate the importers used as stub to call methods
	 * implemented in other plugins.
	 */
	private static PluginComponentImporterFactory importerFactory = GWT
			.create(PluginComponentImporterFactory.class);

	static void handleNewComponent(JavaScriptObject component) {
		Object importedComponent = importerFactory.createFor(component);
		if (importedComponent == null) {
			// unrecognized component -> ignore
			return;
		}
		for (PluginComponentRegisteredListener listener : listeners) {
			listener.pluginComponentRegistered(importedComponent);
		}
	}

	/**
	 * This is the tricky part. Plugins call this method on other plugin's
	 * {@link PluginComponentManager}'s. As in the obfuscated JS code, this
	 * method is probably different for every compiled GWT module, it
	 * theoretically doesn't do what is expected. But the method call is
	 * delegated to a native method that is registered under a defined name.
	 * This native method then calls a java method in the other plugin.
	 * 
	 * @param component
	 */
	final native void callHandleNewComponent(JavaScriptObject component) /*-{
		this.handleNewComponent(component);
	}-*/;

	private static PluginComponentExporterFactory factory = GWT
			.create(PluginComponentExporterFactory.class);

	/**
	 * Exports a component for use in other plugins. See {@link PluginComponent}
	 * for details.
	 * 
	 * @param component
	 *            The component to export. The given object must implement an
	 *            interface that is marked as exportable. See
	 *            {@link PluginComponent} for details.
	 */
	public static final void export(Object component) {
		JavaScriptObject componentExporter = factory.createFor(component);
		PluginRegistry.get().addComponent(componentExporter);
	}

	/**
	 * Adds a listener to get informed whenever a new component is exported by
	 * another plugin. Should be used before {@link #initialize()} to get
	 * already registered components.
	 * 
	 * @param listener
	 *            the listener
	 */
	public static void addListener(PluginComponentRegisteredListener listener) {
		listeners.add(listener);
	}

}
